<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8">
    <title>Sucha's Blog - Archive for September, 2020</title>
    <meta name="generator" content="MarkdownProjectCompositor.lua">
    <meta name="author" content="Sucha">
    <meta name="keywords" content="suchang, programming, Linux, Lua">
    <meta name="description" content="Sucha's blog">
    <link rel="shortcut icon" href="../images/ico.png">
    <link rel="stylesheet" type="text/css" href="../styles/blog.css">
    <link rel="stylesheet" type="text/css" href="../styles/prism.min.css">
    <style id="site_theme"></style>
  </head>
  <body>
    <div id="body">
      <div id="text">
	   <!-- Page published by cmark-gfm begins here --><h1>Sucha's Blog ~ Archive for September, 2020</h1>
<p><a id="p1"></a></p>
<div class="date">20年9月29日 周一 18:25</div>
<h2>Rust(1)</h2>
<p>开始学习 Rust，从 <a href="https://github.com/lalawue/rustlings">rustlings</a> 入手，可以看中文版的 <a href="https://kaisery.github.io/trpl-zh-cn/">Rust 程序设计语言</a>。看了两天书，边看边写习题，完全当成一门新语言来学，内容太厚重了，而且对于我自己已有的经验来说，跟我以往了解的语言，大不一样。</p>
<p>其实 Rust 无需垃圾回收的<a href="https://kaisery.github.io/trpl-zh-cn/ch04-00-understanding-ownership.html">所有权</a>（borrow）部分，为了防止多线程竞态条件的部分，有 C/C++/Java 多线程经验的话，是很好理解的。Rust 虽然也有指针，以我的理解，因为编译器可以确切知道具体引用情况，是不需要多写 *p 这样的了。</p>
<p>深入骨髓的不同，从编译器、语言设计角度来说，是模式匹配、None、trait、宏，字符串、迭代器。</p>
<h3>模式匹配</h3>
<p>模式匹配不仅仅只是 match 关键字，及其控制流，从 let 到 if let 都有模式匹配的影子，如下</p>
<pre><code class="language-rust">let tup = (500, 6.4, 1);
let (x, y, z) = tup;
</code></pre>
<p>上面就是元组的模式匹配，如果数量或类型对应不上，编译期就抛错误。而 if let 是为了简写 match 出现的。因为 Rust 自己的错误处理机制，其实大量用了 if let 或者 match，所以模式匹配一定会遇到的。</p>
<p>Rust 这里还深入提到了一点，有些模式匹配必须成功，程序才能编译通过，比如上面的例子，下面 Some(T) 在其实也是模式匹配，是可以接受匹配失败的。</p>
<h3>None</h3>
<p>跟很多语言不同，Rust 的 None 不算是一个值，而是一种状态，是我的感觉。下面两个是语言固定了的，一定会遇到</p>
<pre><code class="language-rust">enum Result&lt;T, E&gt; {
    Ok(T),
    Err(E),
}

pub enum Option&lt;T&gt; {
    /// No value
    None,
    /// Some value `T`
    Some(T),
}
</code></pre>
<p>Result 用于表明结果是否正确，正确的使用 Ok(T) 返回，错误的用 Err(E) 返回，具体的值需要解包才能使用。Option 表示要么有值，要么没有值，Option::None 没有别的作用，就只是表明没有值而已，如果是 Some(T) 返回的，也是需要解包才能用具体的值。</p>
<p>Lua 的 nil 是 false 含义，还能塞入 table 表示 array 终结，ObjC 的 nil 也是 false 的含义，还能初始化指针，C 里面的 NULL 同理，Java 里面的 null 也是经常用来比较的，还容易引发 NullPointerException，反正是一个大量使用的合理值，新世代的 C++ 我不了解了，但 Rust 不一样，None 就是没有值，是一个枚举定义，我们经常使用且想见到的是 Some(T) 包裹的值。</p>
<h3>trait</h3>
<p>Rust 没有继承，跟 C++/Java 不同，其实鼓励使用组合来完成功能，认为两个不同实例拥有继承关系，因此隐含了大量重复的代码是危险的行为。trait 跟接口很像，但 Java 的 interface 还可以定义函数行为，Rust 这里就不可以了。而且 Rust 教程里面也不会使用 interface 这样已有的词汇来描述 trait，因为 Rust 还有非常严格的类型系统，如果需要做容器的话，会用到很多 trait 提供的能力，比如 Box dyn 之类的。</p>
<pre><code class="language-rust">trait AppendBar {
    fn append_bar(self) -&gt; Self;
}

impl AppendBar for String {
    //Add your code here
    fn append_bar(mut self) -&gt; Self {
        self = self + &amp;&quot;Bar&quot;.to_string();
        self
    }
}
</code></pre>
<p>上面为 String 增加了 append_bar 的接口，添加固定的 &quot;Bar&quot; 后缀。</p>
<p>因为 Rust 的编译器需要在编译期知道所有变量的大小，对于容器来说，是通过 Box 包裹来描述。Rust 为了实现零成本的抽象，泛型的处理实际上很多时候是编译期展开的，只有一些特殊的情况才是运行期动态决定的，这里我了解不深，先略过。</p>
<h3>宏</h3>
<p>Rust 的宏跟 C/C++ 的 define 完全不同，因为不懂 C++ 的 template，所以也回答不上是否拥有 template 这么强大的元编程能力。同样作为元编程，能力强是真的，这个部分我还看不大懂，大概了解到，Rust 宏在 AST 构建完成后才展开，能捕捉下面这些元素</p>
<pre><code class="language-source">item: an item, like a function, struct, module, etc.
block: a block (i.e. a block of statements and/or an expression, surrounded by braces)
stmt: a statement
pat: a pattern
expr: an expression
ty: a type
ident: an identifier
path: a path (e.g. foo, ::std::mem::replace, transmute::&lt;_, int&gt;, …)
meta: a meta item; the things that go inside #[...] and #![...] attributes
tt: a single token tree
</code></pre>
<p>举个例子，下面 vec! 宏捕捉了 expr，push 每个 x</p>
<pre><code class="language-rust">let v: Vec&lt;u32&gt; = vec![1, 2, 3];

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) =&gt; {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
</code></pre>
<p>例子都来自 <a href="https://danielkeep.github.io/tlborm/book/README.html">The Little Book of Rust Macros</a></p>
<h3>字符串</h3>
<p>Rust 内建了 str 类型，表示字符串的纸面含义，是输入字符串原来的值，String 是一个 std 类，内部是 UTF-8 编码的字符串，char 在 Rust 实现是 4 个字节的 UNICODE。</p>
<p>做习题的时候，经常性的 String::From(&quot;abc&quot;) 或者 a.to_string()，习题里面的 char 倒是很少用。</p>
<p>感觉字符串在哪一种语言都不是一个简单的事情，现在都是全球化了，考虑字符串是一种输入，可能是任何一种语言，因此其长度、分割、排版显示都不是小事情。</p>
<h3>迭代器</h3>
<p>Rust 中每种容器都有迭代器，即便是 (0..10) 表示 0 - 9，(0..=10) 表示 0 - 10，可以下面这样用</p>
<pre><code class="language-rust">pub fn factorial(num: u64) -&gt; u64 {
    if num &lt;= 0 {
        0
    } else {
        (1..=num).fold(1, |acc, x| acc * x)
    }
}

assert_eq!(24, factorial(4));
</code></pre>
<p>上面的斐波那契数列计算，没有用到多余的变量，没有用到 for、while、loop 和递归，只用了 Rust 里面的 fold 累加器，其他的还有 sum、filter、map 等，还有，上面忘了说的，expression 放到最后，就直接返回计算的值了。</p>
<p>--</p>
<p>举了好几个跟我之前了解的编程语言大不相同的例子，其实除此之外，还有不少，一些是我还不了解的，比如生命周期，比如 unsafe pointer，生命周期的描述是可以根据一些 trait 控制来改变的。Rust 性能优先，还考虑能高效调用 C 接口，unsafe pointer 是在 Rust 的借用模型、寿命周期外，自己控制程序的行为，毕竟需要调用 C 的接口等等。性能优先，因此零成本抽象、宏等等都是在这个基础上展开的，动态的部分不能说没有，只能说场景很少了。</p>
<p>这两天的心得，大概是这样吧。</p>
<div class="category"><a href="CategoryProgramming.html">CategoryProgramming</a> / <a href="2020-09.html#p1">Permalink</a> / <a href="https://github.com/lalawue/homepage/discussions/categories/blog" target="_blank">Discussion</a></div>
<p><a id="p1"></a></p>
<div class="date">20年9月26日 周六 23:07</div>
<h2>ffi_bitcask.lua</h2>
<p>写了一个 bitcask 模型的 key/value store <a href="https://github.com/lalawue/ffi_bitcask.lua">ffi_bitcask.lua</a>。利用了 LuaJIT cdata memory layout 来写每一个 record 前面 timestamp、crc、fid、ksize、vsize 部分，然后接着写入 key 及 value，读取的时候，先读取每个 record 的前面固定部分，得到 kszie 及 vsize 后，再读取 key 及 value。</p>
<p>ffi_bitcask 的模型，db 目录下有不同的 bucket 目录，bucket 里面可以有相同的 key，bucket 类似于 namespace。每一个 bucket 只有一个活跃文件，所有的 SET/GET/DELETE 操作都是记录，添加到活跃文件末尾。当活跃文件超出阈值后，重新开启一个新的活跃文件，旧的文件是只读的。</p>
<p>我偷了一个懒，所有的活跃文件写入操作，实际上都有重新打开并添加数据，没有像网上介绍的 bitcask 模型一样，一直保留一个打开的文件指针，也许待后面写入速度影响之后，再做这个修改吧，目前自己使用，吞吐量还没到这个份上。</p>
<p>由于删除操作也是记录，原有的记录空间重复占用，只有在下次整理文件时才能回收。我觉得这里自己的算法不好，总共读取了两遍。因为先要扫描出来，哪些是需要删除的条目，并按照文件 id 分类，之后再做一次读取，过滤掉删除的条目，将其他不受影响的条目拷贝到一个新的活跃文件中。</p>
<p>如果有不少删除操作，而许久没有做回收操作，文件目录占用空间比实际使用要多上许多。我自己还加上了将回收后的文件 id 重新利用的逻辑，文件 id 绝对值的增长不会因此影响。</p>
<p>LuaJIT 的 FFI 定义 cdata，内存 layout 跟 C 是一样的，缺点就是 uint64_t 跟 uint32_t 混用的话，是按照 uint64_t 对齐的，另外 Lua 没有 unsigned number 的说法，如果需要用到这个，需要用到 bit.tobit 函数来做比较了。</p>
<p>目前 ffi_bitcask 还缺少的是 hint 文件，现在还不影响，后面可以考虑加上去。还有就是回收操作，其实可以每次只扫描新产生的文件就可以了，因为删除操作只会在之前扫面过后，新生成的数据文件中产生，不过这里需要记录哪些是新生成的文件，如果文件 id 只朝一个方向增加的话是挺好办的，但像我这样重复利用已经回收后的文件 id 的话，就不好处理了。</p>
<p>后面再看看吧。</p>
<div class="category"><a href="CategoryProgramming.html">CategoryProgramming</a> / <a href="2020-09.html#p1">Permalink</a> / <a href="https://github.com/lalawue/homepage/discussions/categories/blog" target="_blank">Discussion</a></div>
<p><a id="p0"></a></p>
<div class="date">20年9月19日 周六 23:57</div>
<h2>Object Storage 和 Shell 环境变量</h2>
<p>找了几个 Object Storage 的方案，理想中的可以是去中心化的方案，然而找不到，找到的要么是 IPFS，要么是链圈的，太高大上了。退而求其次，分布式的的方案，就很多了，各大云商家的对象存储，其实蛮便宜的，还有基于 minio 的，基于 Redis Master-Slave 的方案，及其变种的也算一类，还有豆瓣的 <a href="https://github.com/douban/gobeansdb">gobeandb</a>，以及 <a href="https://prologic.github.io/bitraft/">bitraft</a> 和 <a href="https://github.com/zerotier/lf">lf</a>，一个去中心化的 Key/Value Store.</p>
<p>因为有云服务器了，单独又开对象存储不大值得，因为需要存的东西不多，只是希望至少是分布式的，多个保全而已；minio 的方案，在分布式使用到时候，需要占用单独的磁盘，这个只好放弃了；Redis 及其变种 SSDB 之类的，底层 levelDB，或者占用内存多的放弃了，因为读写的频率很低，而且需要跟其他程序一起占用小排量的云服务器；gobeandb 犹犹豫豫很想用，但需要单独使用一个 goproxy 来分流感觉不大值得，接口是 memcached 的，手头也没有立即能用的解析器；bitraft 试用了一下，感觉最为接近，接口是 Redis 的，但是，无法基于不同数据中心做同步，我的两台云服务器位于不同的服务商它居然无法连接，就不说他默认 value 只有 64k 了；lf 一样没有趁手的接口，而且感觉不好配置。</p>
<p>bitraft 是最接近能用的，而且本地验证同步是很不错的，可惜了。</p>
<p>这里衬托了这么久，只是为了说自己最后写了一个，底层存储基于 <a href="https://dbmx.net/tokyocabinet/">Tokyo Cabinet</a>，有现成的 Lua 绑定，网络服务接口用的是 <a href="https://github.com/lalawue/rpc_framework">rpc_framework</a> 搭建的，同样基于 Redis 协议 <a href="https://github.com/lalawue/lua-resp">lua-resp</a>，同步方案现在很挫的了，一个 master，slave 主动同步 master，启动的时候同步全部 key（也许后面可以优化），然后每隔 30 秒同步一次最新的 SET/DEL 操作；master 这边是每 15 秒区分一个操作 group，操作 group 里面记录所有的 SET/DEL 操作，有 slave 请求，就将 group 时间点及其之后的所有分组 group 操作同步过去；slave 每次都同步上次最新 group 时间点之后的操作数据。</p>
<p>如果 master 这边留了足够多时间跨度的操作 group，后续即便偶尔连不上也是可以保证接下来的数据的；不过目前确实没有考虑 slave 间隔读取不到 master 操作的问题。</p>
<p>为了安全，还加上了 AUTH 命令，简简单单，基本能用了，VALUE 的长度，跟 REDIS 一样是 512M，基本够用了。</p>
<p>Tokyo Cabinet 据说在 2kw 左右的数据量后，会读写缓慢，现在距离这个点还早得很，而且很可能都遇不上。更好的，当然是使用 bitcast 做底层存储，可惜目前也没有方案。</p>
<p>先这样吧，代码就不公开了，单机版在 rpc_framework apps 里做 demo 了。</p>
<p>--</p>
<p>方案找了好久，差不多一周，试用了 bitcast 半天，最后决定用自己的半成品，大概写了 2 天，郁闷的是后面半天时间，每次自己手动启动就正正常常的，但是使用服务推上去就跑不起来，slave 变成了 master。</p>
<p>后来发现，是接受推送，拉起服务的 service，没有使用到包含最新区分不同云服务商的 shell 变量，所以 fork 后拉起来的服务，自然就缺少这个 shell 变量了；手动启动正常，是因为 SSH 进去后每次都读取最新的 bash 配置。</p>
<p>囧啊。</p>
<div class="category"><a href="CategoryProgramming.html">CategoryProgramming</a> / <a href="2020-09.html#p0">Permalink</a> / <a href="https://github.com/lalawue/homepage/discussions/categories/blog" target="_blank">Discussion</a></div>
<!-- Page published by cmark-gfm ends here -->
  <div id="foot">2004-<script>var d = new
	Date();document.write(d.getFullYear())</script> &copy;
	Sucha. Powered by MarkdownProjectCompositor.
  </div>
  </div><!-- text -->
  <div id="sidebar">
  </div><!-- sidebar -->
  <script src="../js/prism.min.js" async="async"></script>
  <script src="../js/blog_sidebar.js"></script>
  </div> <!-- body -->
</body>
</html>